using FluentValidation.AspNetCore;
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Production.ApplicationLib.Queries;
using Production.CommonLib.Constants;
using Production.DomainLib.AggregatesModel.ProcessAggregate;
using Production.DomainLib.AggregatesModel.ProductionPlanAggregate;
using Production.DomainLib.AggregatesModel.ProductionResourceAggregate;
using Production.DomainLib.AggregatesModel.StandardBomAggregate;
using Production.DomainLib.AggregatesModel.TechnologyAggregate;
using Production.InfrastructureLib;
using Production.InfrastructureLib.Repositories;
using Serilog;
using Serilog.Events;
using Swimj.AspNetCore.Infrastructure.Auth;
using Swimj.AspNetCore.Infrastructure.Logging;
using Swimj.AspNetCore.Options;
using Swimj.Core.Providers;
using System.Reflection;
using System.Security.Claims;
using System.Text;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Production.DomainLib.AggregatesModel.ProductionOrderAggregate;

namespace Production.WebApi
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
                .Enrich.FromLogContext()
                .WriteTo.Console()
                .CreateBootstrapLogger();

            Log.Information("Starting web host");


            var builder = WebApplication.CreateBuilder(args);
            builder.Host.UseSerilog((context, _, configuration) =>
                configuration.ReadFrom.Configuration(context.Configuration));

            // Add services to the container.
            builder.Services.AddHttpContextAccessor();
            builder.Services.AddCurrentUser();

            builder.Services.AddSingleton<IDateTimeProvider, LocalDataTimeProvider>();

            var defaultConnStr = builder.Configuration.GetConnectionString("Default");

            builder.Services.AddDbContext<ProductionDbContext>(
                options =>
                {
                    options.UseSqlServer(defaultConnStr, sqlOptions =>
                    {
                        sqlOptions.MigrationsAssembly(typeof(Program).Assembly.FullName);

                        // Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency 

                        sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30),
                            errorNumbersToAdd: null);
                    });
                }); //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request)

            
            // 添加身份验证
            builder.Services
                .AddAuthentication(authentication =>
                {
                    authentication.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    authentication.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(configureOptions =>
                {
                    var key = builder.Configuration.GetValue<string>(
                        $"SecuritySettings:{nameof(JwtSettings)}:{nameof(JwtSettings.Key)}");

                    configureOptions.RequireHttpsMetadata = false;
                    configureOptions.SaveToken = true;
                    configureOptions.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)),
                        ValidateIssuer = false,
                        ValidateAudience = false,
                        RoleClaimType = ClaimTypes.Role,
                        ClockSkew = TimeSpan.Zero
                    };
                });


            builder.Services.AddAuthorization(options =>
            {
                // Here I stored necessary permissions/roles in a constant
                foreach (var prop in typeof(ApiRoutes).GetNestedTypes().SelectMany(c =>
                             c.GetFields(BindingFlags.Public | BindingFlags.Static |
                                         BindingFlags.FlattenHierarchy)))
                {
                    options.AddPolicy(prop.Name,
                        policy => policy.RequireClaim("Permission",
                            prop.Name));
                }
            });

            builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

            builder.Services.AddMediatR(cfg =>
            {
                cfg.RegisterServicesFromAssembly(Assembly.Load("Production.ApplicationLib"));
            });

            builder.Services.AddScoped<IProductionPlanRepository, ProductionPlanRepository>();
            builder.Services.AddScoped<IProcessRepository, ProcessRepository>();
            builder.Services.AddScoped<IProductionOrderRepository, ProductionOrderRepository>();
            builder.Services.AddScoped<ITechnologyRepository, TechnologyRepository>();
            builder.Services.AddScoped<IStandardBomRepository, StandardBomRepository>();
            builder.Services.AddScoped<IProductionResourceRepository, ProductionResourceRepository>();
            builder.Services.AddScoped<IProcessQueries>(sp => new ProcessQueries(defaultConnStr));
            builder.Services.AddScoped<IProductionOrderQueries>(sp => new ProductionOrderQueries(defaultConnStr));
            builder.Services.AddScoped<ITechnologyQueries>(sp => new TechnologyQueries(defaultConnStr));
            builder.Services.AddScoped<IStandardBomQueries>(sp => new StandardBomQueries(defaultConnStr));
            builder.Services.AddScoped<IProductionResourceQueries>(sp => new ProductionResourceQueries(defaultConnStr));
            
            builder.Services.AddControllers();
            
            builder.Services.AddHealthChecks()
                .AddCheck("self", () => HealthCheckResult.Healthy())
                .AddSqlServer(defaultConnStr ?? string.Empty,
                    name: "ProductionDB-check",
                    tags: new[] { "ProductionDB" });
            
            builder.Services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy",
                    corsPolicyBuilder => corsPolicyBuilder
                        .SetIsOriginAllowed(_ => true)
                        .AllowAnyMethod()
                        .AllowAnyHeader()
                        .AllowCredentials());
            });

            builder.Services.AddFluentValidationAutoValidation();
            builder.Services.AddFluentValidationClientsideAdapters();

            builder.Services
                .AddRequestLogging(builder.Configuration)
                .AddExceptionMiddleware();

            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen(c =>
            {
                c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                {
                    Name = "Authorization",
                    In = ParameterLocation.Header,
                    Type = SecuritySchemeType.ApiKey,
                    Scheme = "Bearer",
                    BearerFormat = "JWT",
                    Description =
                        "Input your Bearer token in this format - Bearer {your token here} to access this API",
                });
                c.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference
                            {
                                Type = ReferenceType.SecurityScheme,
                                Id = "Bearer",
                            },
                            Scheme = "Bearer",
                            Name = "Bearer",
                            In = ParameterLocation.Header,
                        },
                        new List<string>()
                    },
                });
            });

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            // app.UseHttpsRedirection();

            app.UseCors("CorsPolicy");

            app.UseRouting();

            app.UseAuthentication();

            app.UseCurrentUser();

            app.UseAuthorization();

            app.MapControllers();

            app.MapHealthChecks("/hc", new HealthCheckOptions()
            {
                Predicate = _ => true,
                ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
            });

            app.MapHealthChecks("/liveness", new HealthCheckOptions
            {
                Predicate = r => r.Name.Contains("self")
            });

            app.Run();
        }
    }
}